#include <task.h>
#include <core/ipc.h>
#include <core/sched.h>
#include <uaccess.h>
#include <malloc.h>
#include <log.h>

/**
 * A helper function to transfer all the ipc_msg's capbilities of client's
 * process to server's process
 */
int ipc_send_cap(ipc_connection_t *conn, ipc_msg_t *ipc_msg)
{
    u64_t slot_count;
    u64_t cap_slots_offset;

    copy_from_user((char *)&slot_count, (char *)&ipc_msg->slot_count, sizeof(slot_count));
    copy_from_user((char *)&cap_slots_offset, (char *)&ipc_msg->cap_slots_offset, sizeof(cap_slots_offset));

    if (slot_count == 0)
        return 0;

    slot_t *slot_buf = malloc(slot_count * sizeof(slot_t));
    copy_from_user((char *)slot_buf, (char *)ipc_msg + cap_slots_offset, sizeof(slot_t) * slot_count);

    for (int i = 0; i < slot_count; i++)
    {
        if (slot_buf[i] == -1)
        {
            LOGW("slot == -1");
            continue;
        }
        slot_t dest_slot = slot_copy(process_self(), conn->target->process, slot_buf[i]);
        slot_buf[i] = dest_slot;
    }

    copy_to_user((char *)ipc_msg + cap_slots_offset, (char *)slot_buf, sizeof(slot_t) * slot_count);

    free(slot_buf);
    return 0;
}

/**
 * Client thread calls this function and then return to server thread
 * This function should never return
 */
static u64_t thread_migrate_to_server(ipc_connection_t *conn, u64_t arg)
{
    thread_t *target = conn->target;
    conn->source = thread_self();
    target->active_conn = conn;

    struct pt_regs *regs = task_pt_regs(target);

    regs->sp = (uintptr_t)conn->server_stack_top;
    regs->a0 = (uintptr_t)arg;
    regs->sepc = (uintptr_t)conn->target->server_ipc_config->callback;

    void ret_from_exception();
    // ipc_return sets context,  reset it
    target->context.ra = (unsigned long)ret_from_exception;
    target->context.sp = (unsigned long)regs;

    schedule_to(target);
    return 0;
}

/**
 * The client thread calls sys_ipc_call to migrate to the server thread.
 * When you transfer the ipc_msg (which is the virtual address in the client
 * vmspace), do not forget to change the virtual address to server's vmspace.
 * This function should never return!
 */
u64_t sys_ipc_call(u32_t conn_cap, ipc_msg_t *ipc_msg)
{
    ipc_connection_t *conn = dynamic_cast(ipc_connection_t)(slot_get(process_self(), conn_cap));

    if (ipc_msg != NULL)
    {
        // transfer all the capbiliies of client thread to
        copy_to_user((char *)&ipc_msg->client_slot, (char *)&conn->client_slot, sizeof(u64_t));
        copy_to_user((char *)&ipc_msg->server_conn_slot, (char *)&conn->server_conn_slot, sizeof(u64_t));
        ipc_send_cap(conn, ipc_msg);
    }

    u64_t arg = conn->buf.server_user_addr;
    thread_migrate_to_server(conn, arg);
    return conn->return_value;
}